﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;

namespace App33
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        MainPageViewModel ViewModel { get { return this.DataContext as MainPageViewModel; } }

        DispatcherTimer _Timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(.5) };
        private void Hub_Loaded(object sender, RoutedEventArgs args)
        {
            // initi hub setup
            var hub = sender as Hub;
            this.ViewModel.UpdateBitmap(hub.Sections.First());
            UpdateHeaders(hub);

            // setup snap
            var scrollviewer = Helpers.Children(hub).First(x => x is ScrollViewer) as ScrollViewer;
            scrollviewer.HorizontalSnapPointsType = SnapPointsType.Mandatory;
            scrollviewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;

            // update headers when user scrolls
            scrollviewer.ViewChanged += (s, e) => UpdateHeaders(hub);

            // update last section when user resizes or snaps
            Window.Current.SizeChanged += (s, e) =>
            {
                this.ViewModel.NinetyPercent = Window.Current.Bounds.Width * .9;
                this.ViewModel.UpdateBitmap(hub.Sections.First());
            };
        }

        // any section is tapped, scroll to it
        private void HubSection_Tapped(object sender, TappedRoutedEventArgs e) { ScrollToSection(sender as HubSection); }

        // header text is tapped, scroll to corresponding section
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var button = sender as Button;
            var header = button.DataContext as MainPageViewModel.HeaderInfo;
            ScrollToSection(header.Section);
        }

        // update headers means keep them in the order of the pivot
        private async void UpdateHeaders(Hub hub, HubSection section = null)
        {
            await Task.Delay(500);
            this.ViewModel.Headers.Clear();
            var current = section ?? hub.SectionsInView.First();
            var currentIndex = hub.Sections.IndexOf(current);
            var list = hub.Sections.Skip(currentIndex).Union(hub.Sections.Take(currentIndex));
            foreach (var item in list)
            {
                if (item == hub.Sections.Last())
                    continue;
                var header = new MainPageViewModel.HeaderInfo
                {
                    Header = item.Header.ToString(),
                    Index = hub.Sections.IndexOf(item),
                    Opacity = (item == current) ? 1d : .5,
                    Section = item,
                };
                this.ViewModel.Headers.Add(header);
            }
        }

        // scroll to view will animate the action
        void ScrollToSection(HubSection section)
        {
            var hub = Helpers.Parents(section).First(x => x is Hub) as Hub;
            if (hub.SectionsInView.First() == section)
            {
                // do nothing if it is already the primary pivot item
                return;
            }
            else if (hub.Sections.Last() == section)
            {
                // last section scrolls to first
                var scrollviewer = Helpers.Children(hub).First(x => x is ScrollViewer) as ScrollViewer;
                scrollviewer.ChangeView(0, null, null);
                UpdateHeaders(hub, section);
            }
            else
            {
                // middle sections will scroll to that section
                var scrollviewer = Helpers.Children(hub).First(x => x is ScrollViewer) as ScrollViewer;
                scrollviewer.ChangeView(section.ActualWidth * (hub.Sections.IndexOf(section)), null, null);
                UpdateHeaders(hub, section);
            }
        }
    }

    class MainPageViewModel : BindableBase
    {
        public MainPageViewModel()
        {
            // designtime headers
            if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
                this._Headers = new ObservableCollection<HeaderInfo>(Enumerable.Range(1, 4).Select(x => new HeaderInfo { Header = "!Section " + x.ToString(), Index = x, Opacity = 1 }));

            // designtime size
            this.NinetyPercent = Window.Current.Bounds.Width * .9;
        }

        public async void UpdateBitmap(Control control)
        {
            // last setup section
            var render = new Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap();
            await render.RenderAsync(control);
            this.Bitmap = render;
        }

        // image that represents the last section
        RenderTargetBitmap _Bitmap = default(RenderTargetBitmap);
        public RenderTargetBitmap Bitmap { get { return _Bitmap; } set { base.SetProperty(ref _Bitmap, value); } }

        // prescribed width of the pivot item
        double _NinetyPercent = default(double);
        public double NinetyPercent { get { return _NinetyPercent; } set { base.SetProperty(ref _NinetyPercent, value); } }

        // dto for a header
        public class HeaderInfo
        {
            public string Header { get; set; }
            public int Index { get; set; }
            public double Opacity { get; set; }
            public HubSection Section { get; set; }
        }

        // list of headers (can't bind directly to sections collection)
        ObservableCollection<HeaderInfo> _Headers = new ObservableCollection<HeaderInfo>();
        public ObservableCollection<HeaderInfo> Headers { get { return _Headers; } }
    }

    static class Helpers
    {
        public static IEnumerable<Control> Children(Control element)
        {
            Func<DependencyObject, List<Control>> recurseChildren = null;
            recurseChildren = (parent) =>
            {
                var list = new List<Control>();
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
                {
                    var child = VisualTreeHelper.GetChild(parent, i);
                    if (child is Control)
                        list.Add(child as Control);
                    list.AddRange(recurseChildren(child));
                }
                return list;
            };
            var children = recurseChildren(element);
            return children;
        }

        public static IEnumerable<Control> Parents(Control element)
        {
            var list = new List<Control>();
            DependencyObject parent = VisualTreeHelper.GetParent(element);
            while (parent != null)
            {
                if (parent is Control)
                    list.Add(parent as Control);
                parent = VisualTreeHelper.GetParent(parent);
            }
            return list;
        }
    }

    // helper class
    abstract class BindableBase : System.ComponentModel.INotifyPropertyChanged
    {
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
        {
            if (!object.Equals(storage, value))
            {
                storage = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
        protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}
